﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Foundation.Sugar;
using Protocol.Data;

namespace Protocol
{
    public static class ByteConverter
    {
        private static readonly Dictionary<int, Type> Types = new Dictionary<int, Type>();
        private static readonly Dictionary<Type, int> Constructors = new Dictionary<Type, int>();

        static ByteConverter()
        {
            var types = Assembly.GetCallingAssembly()
                .GetTypes()
                .Where(t => Attribute.IsDefined(t, typeof (IdAttribute)));
            foreach (var type in types)
            {
                var attribute = Attribute.GetCustomAttribute(type, typeof (IdAttribute));
                var constructorCode = attribute.Of<IdAttribute>().Key;
                Types.Add(constructorCode, type);
                Constructors.Add(type, constructorCode);
            }
        }

        public static byte[] ToBytes(this object data)
        {
            var type = data.GetType();
            if (Types.Values.Contains(type) || type == typeof(Vector<long>))
                return ToBytesT(data, type).ToArray();
            return new byte[0];
        }

        public static object FromBytes(this IEnumerable<byte> iterator)
        {
            return FromBytes(ref iterator);
        }

        public static object FromBytes(ref IEnumerable<byte> iterator)
        {
            // ReSharper disable PossibleMultipleEnumeration
            var constructorBytes = iterator.Take(4).ToArray();
            iterator = iterator.Skip(4);
            var constructorCode = BitConverter.ToInt32(constructorBytes, 0);
            var strCode = constructorCode.ToString("x8");
            strCode = strCode;
            if (!Types.ContainsKey(constructorCode))
                return constructorCode;
            var type = Types[constructorCode];


            if (type.IsGenericType)
            {
                var item = Activator.CreateInstance(type.MakeGenericType(new[] {typeof (long)}));
                var countBytes = iterator.Take(4).ToArray();
                iterator = iterator.Skip(4);
                var count = BitConverter.ToInt32(countBytes, 0);
                var a = (IList) item;
                for (var i = 0; i < count; i++)
                {
                    a.Add(GetValue(iterator.Take(8).ToArray(), typeof (long)));
                    iterator = iterator.Skip(8);
                }

                return a;
            }
            else
            {
                var item = Activator.CreateInstance(type);
                var properties = type.GetProperties().ToList();
                foreach (var property in properties)
                {
                    var valueType = property.PropertyType;
                    if (valueType == typeof (Vector<object>))
                    {
                        var countBytes = iterator.Take(4).ToArray();
                        var count = BitConverter.ToInt32(countBytes, 0);
                        iterator = iterator.Skip(4);
                        var vector = new Vector<object>();
                     
                        for (int i = 0; i < count; i++)
                        {
                            var data = iterator.ToArray();
                            iterator = data.Skip(16);
                            var message = FromBytes(ref iterator);
                            var msgLen = message.ToBytes().Length;
                            iterator = data.Skip(msgLen + 16);
                            var xx = iterator.ToArray();
                            vector.Add(message);
                        }

                        property.SetValue(item, vector, null);
                    }
                    else if (valueType.IsGenericType)
                    {
                        var propertyValue = FromBytes(ref iterator);
                        property.SetValue(item, propertyValue, null);
                    }
                    else
                    {
                        var attribute =
                            Attribute.GetCustomAttribute(property, typeof (LengthAttribute)).As<LengthAttribute>();
                        var skipLength = attribute == null ? 0 : attribute.Length;
                        var propertyValueBytes = attribute == null
                            ? TakeBytes(iterator, valueType, out skipLength)
                            : iterator.Take(attribute.Length).ToArray();

                        iterator = iterator.Skip(skipLength);
                        var propertyValue = GetValue(propertyValueBytes, property.PropertyType);
                        property.SetValue(item, propertyValue, null);
                    }
                }

                return item;
            }
        }

        public static object GetValue(byte[] bytes, Type type)
        {
            if (type == typeof (int)) return BitConverter.ToInt32(bytes, 0);
            if (type == typeof (uint)) return BitConverter.ToUInt32(bytes, 0);
            if (type == typeof (long)) return BitConverter.ToInt64(bytes, 0);
            if (type == typeof (ulong)) return BitConverter.ToUInt64(bytes, 0);
            if (type == typeof (string)) return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
            if (type == typeof (BigInteger)) return new BigInteger(bytes.ToArray());

            return bytes;
        }

        public static byte[] TakeBytes(IEnumerable<byte> data, Type propertyType, out int skipTotal)
        {
            int take;
            var skip = 0;
            if (propertyType == typeof (long) || propertyType == typeof (ulong)) take = 8;
            else if (propertyType == typeof (int) || propertyType == typeof (uint)) take = 4;
            else
            {
                take = data.First();
                if (take >= 254)
                    take = data.Skip(1).First() + (data.Skip(2).First() << 8) + (data.Skip(3).First() << 16);


                skip = take >= 254 ? 4 : 1;
            }

            var bytes = data.Skip(skip).Take(take);
            skipTotal = skip + take;
            var tail = skipTotal%4;
            if (tail > 0) skipTotal += (4 - tail);
            return bytes.ToArray();
        }

        private static List<byte> ToBytesT(object o, Type t)
        {
            if (o is Vector<long>)
            {
                var vector = o.Of<Vector<long>>();
                var result = new List<byte>();
                var attribute = Attribute.GetCustomAttribute(t, typeof (IdAttribute));
                var constructorCode = attribute.Of<IdAttribute>().Key;
                result.AddRange(BitConverter.GetBytes(constructorCode));
                result.AddRange(BitConverter.GetBytes(vector.Count));
                vector.ForEach(i => result.AddRange(BitConverter.GetBytes(i)));
                return result;
            }
            else
            {
                var properties = t.GetProperties().ToList();
                var result = new List<byte>();
                var attribute = Attribute.GetCustomAttribute(t, typeof (IdAttribute));
                var constructorCode = attribute.Of<IdAttribute>().Key;
                result.AddRange(BitConverter.GetBytes(constructorCode));
                foreach (var property in properties)
                {
                    var propertyType = property.PropertyType;
                    var propertyValue = property.GetValue(o, null);
                    byte[] propertyBytes = null;
                    if (propertyType == typeof (byte[]))
                        propertyBytes = propertyValue.Of<byte[]>().Length > 32
                            ? ToTLString(propertyValue.Of<byte[]>())
                            : (byte[]) propertyValue;
                    else if (propertyType == typeof (int)) propertyBytes = BitConverter.GetBytes((int) propertyValue);
                    else if (propertyType == typeof (long)) propertyBytes = BitConverter.GetBytes((long) propertyValue);
                    else if (propertyType == typeof (uint)) propertyBytes = BitConverter.GetBytes((uint) propertyValue);
                    else if (propertyType == typeof (ulong)) propertyBytes = BitConverter.GetBytes((ulong) propertyValue);
                    else if (propertyType == typeof (string)) propertyBytes = Encoding.UTF8.GetBytes((string) propertyValue);
                    else if (propertyType == typeof (BigInteger)) propertyBytes = ToTLString(propertyValue.Of<BigInteger>().getBytes());
                    else if (propertyType == typeof (Vector<long>)) propertyBytes = propertyValue.ToBytes();

                    if (propertyBytes != null) result.AddRange(propertyBytes);
                }

                return result;
            }
        }

        public static byte[] ToTLString(this byte[] bytes)
        {
            var result = new List<byte>();
            if (bytes.Length >= 254)
            {
                result.Add(254);
                result.Add((byte) (bytes.Length & 0xFF));
                result.Add((byte) ((bytes.Length >> 8) & 0xFF));
                result.Add((byte) ((bytes.Length >> 16) & 0xFF));
            }
            else
            {
                result.Add((byte) bytes.Length);
            }

            result.AddRange(bytes);
            while (result.Count%4 != 0) result.Add(new byte());
            return result.ToArray();
        }
    }
}
